// Copyright 2017 Baidu Inc. All Rights Reserved.
// Author: Chen Xihao (chenxihao@baidu.com)
//
// Description: play http live stream

#include "baidu_hls.h"
#include <assert.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include "duer_log.h"
#include "heap_monitor.h"
#include "baidu_hls_adapter.h"

#define LOGD(...) DUER_LOGD(__VA_ARGS__)
#define LOGI(...) DUER_LOGI(__VA_ARGS__)
#define LOGW(...) DUER_LOGW(__VA_ARGS__)
#define LOGE(...) DUER_LOGE(__VA_ARGS__)
#define PRINT(...) printf(__VA_ARGS__)

#define MIN_PROTOCOL_VERSION 1
#define MAX_PROTOCOL_VERSION 3
#define MAX_LIVE_M3U8_LENGTH 2048

typedef struct enc_aes128 {
    char*       url;        // key url
    uint8_t*    aes_key;    // AES-128
    bool        b_key_loaded;
    void*       aes_ctx;
    uint8_t*    iv;
} enc_aes128;

typedef struct hls_segment {
    char*               url;
    int                 duration; // segment duration (seconds)
    int                 sequence; // unique sequence number
    enc_aes128          aes128;
    struct hls_segment* next;
} hls_segment;

typedef struct hls_stream_playlist {
    char*               url;
    int                 id;         // program id
    int                 version;    // protocol version should be 1, 2 or 3
    int                 sequence;   // media sequence number
    int                 duration;   // maximum duration per segment (s)
    uint64_t            bandwidth;  // bandwidth usage of segments (bits per second)
    bool                b_updated;
    size_t              position;    // next download postion for vod type m3u8

    int                 count;
    hls_segment*        first_segment;
    hls_segment*        last_segment;

    char*               current_key_path;       // URL path of the encrypted key
    uint8_t             aes_iv[AES_BLOCK_SIZE]; // IV used when decypher the block
    bool                b_iv_loaded;

    struct hls_stream_playlist* next;
} hls_stream_playlist;

typedef struct hls_master_playlist {
    bool                b_live;     // live stream or vod?
    bool                b_error;    // parsing error
    bool                b_aesmsg;   // only print one time that the media is encrypted
    bool                b_meta;     // meta playlist

    char*               url;        // M3U8 url
    int                 count;
    hls_stream_playlist* first_stream;
    hls_stream_playlist* last_stream;
    hls_stream_playlist* current_stream;// best choose of streams

    uint32_t            wakeup;
    void*               lock;
} hls_master_playlist;

static hls_adapter* s_adapter = NULL;
static void* s_reload_thread = NULL;
static volatile int s_stop_flag = 0;

/**
 * returned value should be freed by caller
 */
static char* hls_strdup(const char* str) {
    char* ret = NULL;
    if (str) {
        size_t len = strlen(str) + 1;
        ret = MALLOC(len, MEDIA);
        if (ret) {
            memcpy(ret, str, len);
        }
    }
    return ret;
}

/**
 * returned value should be freed by caller
 */
static char* hls_strndup(const char* str, size_t len) {
    if (!str) {
        return NULL;
    }

    char* end = memchr(str, 0, len);
    if (end) {
        len = end - str;
    }

    char* ret = MALLOC(len + 1, MEDIA);
    if (ret != NULL) {
        memcpy(ret, str, len);
        ret[len] = 0;
    }

    return ret;
}

/* Segment */
static hls_segment* hls_segment_new(
        hls_stream_playlist* stream,
        const int duration,
        const char *uri) {
    hls_segment* segment = (hls_segment*)MALLOC(sizeof(hls_segment), MEDIA);
    if (segment == NULL) {
        return NULL;
    }

    segment->url = hls_strdup(uri);
    if (segment->url == NULL) {
        FREE(segment);
        return NULL;
    }
    segment->duration = duration; /* seconds */
    segment->sequence = 0;
    segment->aes128.url = NULL;
    segment->aes128.aes_key = NULL;
    segment->aes128.b_key_loaded = false;
    segment->aes128.aes_ctx = NULL;
    segment->aes128.iv = NULL;

    if (stream->current_key_path) {
        segment->aes128.url = hls_strdup(stream->current_key_path);
    }
    segment->next = NULL;

    if (stream->first_segment == NULL) {
        stream->first_segment = stream->last_segment = segment;
    } else {
        stream->last_segment->next = segment;
        stream->last_segment = segment;
    }
    stream->count++;

    return segment;
}

static void hls_segment_free(hls_segment *segment) {
    if (segment == NULL) {
        return;
    }

    FREE(segment->url);
    FREE(segment->aes128.url);
    FREE(segment->aes128.aes_key);
    if (segment->aes128.aes_ctx != NULL) {
        s_adapter->aes_context_destroy(segment->aes128.aes_ctx);
    }
    FREE(segment->aes128.iv);
    FREE(segment);
}

static hls_segment* hls_segment_find(hls_stream_playlist* stream, const int sequence) {
    assert(stream);

    if (stream->count <= 0) {
        return NULL;
    }

    hls_segment* segment = stream->first_segment;
    while (segment != NULL) {
        if (segment->sequence == sequence) {
            return segment;
        }
        segment = segment->next;
    }

    return NULL;
}

static hls_stream_playlist* hls_stream_playlist_new(
        hls_master_playlist* playlist,
        const int id,
        const uint64_t bw,
        const char* uri) {
    hls_stream_playlist *stream = (hls_stream_playlist*)MALLOC(sizeof(hls_stream_playlist), MEDIA);
    if (stream == NULL) {
        return NULL;
    }

    stream->url = hls_strdup(uri);
    if (stream->url == NULL){
        FREE(stream);
        return NULL;
    }
    stream->id = id;
    stream->version = MIN_PROTOCOL_VERSION;
    stream->sequence = 0;
    stream->duration = -1;
    stream->bandwidth = bw;
    stream->b_updated = false;
    stream->position = 0;
    stream->count = 0;
    stream->first_segment = NULL;
    stream->last_segment = NULL;
    stream->current_key_path = NULL;
    stream->b_iv_loaded = false;
    stream->next = NULL;

    if (playlist->first_stream == NULL) {
        playlist->first_stream = playlist->last_stream = stream;
    } else {
        playlist->last_stream->next = stream;
        playlist->last_stream = stream;
    }
    playlist->count++;

    return stream;
}

static void hls_stream_playlist_free(hls_stream_playlist* stream) {
    if (stream == NULL) {
        return;
    }

    hls_segment* p = stream->first_segment;
    while (p != NULL) {
        hls_segment* next = p->next;
        hls_segment_free(p);
        p = next;
    }

    FREE(stream->url);
    FREE(stream->current_key_path);
    FREE(stream);
}

static void hls_master_playlist_free(hls_master_playlist* playlist) {
    if (playlist == NULL) {
        return;
    }

    hls_stream_playlist* p = playlist->first_stream;
    while (p != NULL) {
        hls_stream_playlist* next = p->next;
        hls_stream_playlist_free(p);
        p = next;
    }

    FREE(playlist->url);
    FREE(playlist);

    if (playlist->lock != NULL) {
        s_adapter->mutex_destroy(playlist->lock);
    }
}

/**
 * returned value should be freed by caller
 */
static char* read_line(uint8_t* buffer, uint8_t** pos, const size_t len) {
    assert(buffer);

    char* line = NULL;
    uint8_t* begin = buffer;
    uint8_t* p = begin;
    uint8_t* end = p + len;

    while (p < end) {
        if ((*p == '\r') || (*p == '\n') || (*p == '\0')) {
            break;
        }

        p++;
    }

    /* copy line excluding \r \n or \0 */
    line = hls_strndup((char*)begin, p - begin);

    while ((*p == '\r') || (*p == '\n') || (*p == '\0')) {
        if (*p == '\0') {
            *pos = end;
            break;
        } else {
            /* next pass start after \r and \n */
            p++;
            *pos = p;
        }
    }

    return line;
}

static char* parse_attributes(const char* line, const char* attr) {
    char* p = NULL;
    char* begin = (char*)line;
    char* end = begin + strlen(line);

    /* Find start of attributes */
    if ((p = strchr(begin, ':')) == NULL) {
        return NULL;
    }

    begin = p;

    do {
        if (strncasecmp(begin, attr, strlen(attr)) == 0
                && begin[strlen(attr)] == '=') {
            /* <attr>="<value>"[,]* */
            p = strchr(begin, ',');
            begin += strlen(attr) + 1;

            /* Check if we have " " marked value*/
            if (begin[0] == '"') {
                char* valueend = strchr(begin + 1, '"');

                /* No ending " so bail out */
                if (!valueend) {
                    return NULL;
                }

                p = strchr(valueend, ',');
            }

            if (begin >= end) {
                return NULL;
            }

            if (p == NULL) { /* last attribute */
                return hls_strndup(begin, end - begin);
            }

            /* copy till ',' */
            return hls_strndup(begin, p - begin);
        }

        begin++;
    } while (begin < end);

    return NULL;
}

static int string_to_iv(char* string_hexa, uint8_t iv[AES_BLOCK_SIZE]) {
    uint64_t iv_hi = 0;
    uint64_t iv_lo = 0;
    char* end = NULL;

    if (*string_hexa++ != '0') {
        return HLS_FAILED;
    }

    if (*string_hexa != 'x' && *string_hexa != 'X') {
        return HLS_FAILED;
    }

    string_hexa++;

    size_t len = strlen(string_hexa);

    if (len <= 16) {
        iv_hi = 0;
        iv_lo = strtoull(string_hexa, &end, 16);

        if (*end) {
            return HLS_FAILED;
        }
    } else {
        iv_lo = strtoull(&string_hexa[len - 16], &end, 16);

        if (*end) {
            return HLS_FAILED;
        }

        string_hexa[len - 16] = '\0';
        iv_hi = strtoull(string_hexa, &end, 16);

        if (*end) {
            return HLS_FAILED;
        }
    }

    for (int i = 7; i >= 0 ; --i) {
        iv[i] = iv_hi & 0xff;
        iv[8 + i] = iv_lo & 0xff;
        iv_hi >>= 8;
        iv_lo >>= 8;
    }

    return HLS_SUCCESS;
}

static char* relative_uri(const char* psz_url, const char* psz_path) {
    char* ret = NULL;
    const char* fmt;
    assert(psz_url != NULL && psz_path != NULL);


    //If the path is actually an absolute URL, don't do anything.
    if (strncmp(psz_path, "http", 4) == 0) {
        return NULL;
    }

    size_t len = strlen(psz_path);

    char* new_url = hls_strdup(psz_url);

    if (!new_url) {
        return NULL;
    }

    if (psz_path[0] == '/') { //Relative URL with absolute path
        //Try to find separator for name and path, try to skip
        //access and first ://
        char* slash = strchr(&new_url[8], '/');

        if (slash == NULL) {
            goto end;
        }

        *slash = '\0';
        fmt = "%s%s";
    } else {
        int levels = 0;

        while (len >= 3 && !strncmp(psz_path, "../", 3)) {
            psz_path += 3;
            len -= 3;
            levels++;
        }

        do {
            char* slash = strrchr(new_url, '/');

            if (slash == NULL) {
                goto end;
            }

            *slash = '\0';
        } while (levels--);

        fmt = "%s/%s";
    }

    if (asprintf(&ret, fmt, new_url, psz_path) < 0) {
        ret = NULL;
    }

end:
    FREE(new_url);
    return ret;
}

static int parse_segment_information(hls_stream_playlist* stream, char* p_read, int* duration) {
    assert(stream);
    assert(p_read);
    assert(duration);

    /* strip of #EXTINF: */
    char* token = strtok(p_read, ":");

    if (token == NULL) {
        return HLS_FAILED;
    }

    /* read duration */
    token = strtok(NULL, ",");

    if (token == NULL) {
        return HLS_FAILED;
    }

    int value = 0;
    char* endptr = NULL;

    if (stream->version < MAX_PROTOCOL_VERSION) {
        errno = 0;
        value = strtol(token, &endptr, 10);

        if (token == endptr || errno == ERANGE) {
            *duration = -1;
            return HLS_FAILED;
        }

        *duration = value;
    } else {
        errno = 0;
        double d = strtof(token, &endptr);

        if (token == endptr || errno == ERANGE) {
            *duration = -1;
            return HLS_FAILED;
        }

        if ((d) - ((int)d) >= 0.5) {
            value = ((int)d) + 1;
        } else {
            value = ((int)d);
        }

        *duration = value;
    }

    /* Ignore the rest of the line */
    return HLS_SUCCESS;
}

static int parse_add_segment(hls_stream_playlist* stream, const int duration, const char* uri) {
    assert(stream);
    assert(uri);

    char* psz_uri = relative_uri(stream->url, uri);

    hls_segment* segment = hls_segment_new(stream, duration, psz_uri ? psz_uri : uri);

    if (segment) {
        segment->sequence = stream->sequence + stream->count - 1;
    }

    FREE(psz_uri);

    return segment ? HLS_SUCCESS : HLS_NOMEMORY;
}

static int parse_target_duration(hls_stream_playlist* stream, char* p_read) {
    assert(stream);

    int duration = -1;
    int ret = sscanf(p_read, "#EXT-X-TARGETDURATION:%d", &duration);

    if (ret != 1) {
        LOGE("expected #EXT-X-TARGETDURATION:<s>");
        return HLS_FAILED;
    }

    stream->duration = duration; /* seconds */
    return HLS_SUCCESS;
}

static int parse_stream_information(
        hls_master_playlist* playlist,
        char* p_read,
        const char* uri) {
    char* attr = parse_attributes(p_read, "PROGRAM-ID");

    int id = 0;
    if (attr) {
        id = atol(attr);
        FREE(attr);
    } else {
        id = 0;
    }

    attr = parse_attributes(p_read, "BANDWIDTH");

    if (attr == NULL) {
        LOGE("#EXT-X-STREAM-INF: expected BANDWIDTH=<value>");
        return HLS_FAILED;
    }

    uint64_t bw = atoll(attr);
    FREE(attr);

    if (bw == 0) {
        LOGE("#EXT-X-STREAM-INF: bandwidth cannot be 0");
        return HLS_FAILED;
    }

    LOGD("bandwidth adaptation detected (program-id=%d, bandwidth=%lld).", id, bw);

    char* psz_uri = relative_uri(playlist->url, uri);

    hls_stream_playlist* stream = hls_stream_playlist_new(
            playlist, id, bw, psz_uri ? psz_uri : uri);

    FREE(psz_uri);

    return (stream == NULL) ? HLS_NOMEMORY : HLS_SUCCESS;
}

static int parse_media_sequence(
        hls_master_playlist* playlist,
        hls_stream_playlist* stream,
        char* p_read) {
    assert(stream);

    int sequence = 0;
    int ret = sscanf(p_read, "#EXT-X-MEDIA-SEQUENCE:%d", &sequence);

    if (ret != 1) {
        LOGE("expected #EXT-X-MEDIA-SEQUENCE:<s>");
        return HLS_FAILED;
    }

    if (stream->sequence > 0) {
        if (playlist->b_live) {
            hls_segment* last_segment = stream->last_segment;

            if ((last_segment->sequence < sequence) &&
                    (sequence - last_segment->sequence >= 1)) {
                LOGE("EXT-X-MEDIA-SEQUENCE gap in playlist (new=%d, old=%d)",
                        sequence, last_segment->sequence);
            }
        } else {
            LOGE("EXT-X-MEDIA-SEQUENCE already present in playlist (new=%d, old=%d)",
                    sequence, stream->sequence);
        }
    }

    stream->sequence = sequence;

    return HLS_SUCCESS;
}

static int parse_key(hls_master_playlist* playlist, hls_stream_playlist* stream, char* p_read) {
    assert(playlist);
    assert(stream);

    /* #EXT-X-KEY:METHOD=<method>[,URI="<URI>"][,IV=<IV>] */
    int err = HLS_SUCCESS;
    char* attr = parse_attributes(p_read, "METHOD");

    if (attr == NULL) {
        LOGE("#EXT-X-KEY: expected METHOD=<value>");
        return err;
    }

    if (strncasecmp(attr, "NONE", 4) == 0) {
        char* uri = parse_attributes(p_read, "URI");

        if (uri != NULL) {
            LOGE("#EXT-X-KEY: URI not expected");
            err = HLS_FAILED;
        }

        FREE(uri);

        /* IV is only supported in version 2 and above */
        if (stream->version >= 2) {
            char* iv = parse_attributes(p_read, "IV");

            if (iv != NULL) {
                LOGE("#EXT-X-KEY: IV not expected");
                err = HLS_FAILED;
            }

            FREE(iv);
        }
    } else if (strncasecmp(attr, "AES-128", 7) == 0) {
        char* value = NULL;
        char* uri = NULL;
        char* iv = NULL;

        if (playlist->b_aesmsg == false) {
            LOGI("playback of AES-128 encrypted HTTP Live media detected.");
            playlist->b_aesmsg = true;
        }

        value = uri = parse_attributes(p_read, "URI");

        if (value == NULL) {
            LOGE("#EXT-X-KEY: URI not found for encrypted HTTP Live media in AES-128");
            FREE(attr);
            return HLS_FAILED;
        }

        /* Url is put between quotes, remove them */
        if (*value == '"') {
            /* We need to strip the "" from the attribute value */
            uri = value + 1;
            char* end = strchr(uri, '"');

            if (end != NULL) {
                *end = 0;
            }
        }

        if (stream->current_key_path != NULL) {
            FREE(stream->current_key_path);
            stream->current_key_path = NULL;
        }

        /* For absolute URI, just duplicate it
         * don't limit to HTTP, maybe some sanity checking
         * should be done more in here? */
        if (strstr(uri, "://")) {
            stream->current_key_path = hls_strdup(uri);
        } else {
            stream->current_key_path = relative_uri(stream->url, uri);
        }

        FREE(value);

        value = iv = parse_attributes(p_read, "IV");

        if (iv == NULL) {
            /*
            * If the EXT-X-KEY tag does not have the IV attribute, implementations
            * MUST use the sequence number of the media file as the IV when
            * encrypting or decrypting that media file.  The big-endian binary
            * representation of the sequence number SHALL be placed in a 16-octet
            * buffer and padded (on the left) with zeros.
            */
            stream->b_iv_loaded = false;
        } else {
            /*
            * If the EXT-X-KEY tag has the IV attribute, implementations MUST use
            * the attribute value as the IV when encrypting or decrypting with that
            * key.  The value MUST be interpreted as a 128-bit hexadecimal number
            * and MUST be prefixed with 0x or 0X.
            */

            if (string_to_iv(iv, stream->aes_iv) == HLS_FAILED) {
                LOGE("IV invalid");
                err = HLS_FAILED;
            } else {
                stream->b_iv_loaded = true;
            }

            FREE(value);
        }
    } else {
        LOGW("playback of encrypted HTTP Live media is not supported.");
        err = HLS_FAILED;
    }

    FREE(attr);
    return err;
}

static int parse_version(hls_stream_playlist* stream, char* p_read) {
    assert(stream);

    int version = 0;
    int ret = sscanf(p_read, "#EXT-X-VERSION:%d", &version);

    if (ret != 1) {
        LOGE("#EXT-X-VERSION: no protocol version found, should be version 1.");
        return HLS_FAILED;
    }

    /* Check version */
    stream->version = version;

    if (stream->version < MIN_PROTOCOL_VERSION || stream->version > MAX_PROTOCOL_VERSION) {
        LOGE("#EXT-X-VERSION should be version 1, 2 or 3 iso %d", version);
        return HLS_FAILED;
    }

    return HLS_SUCCESS;
}

static int parse_m3u8(hls_master_playlist* playlist, uint8_t* buffer,
                      const size_t len, bool b_parent_is_meta) {
    uint8_t* p_read = NULL;
    uint8_t* p_begin = buffer;
    uint8_t* p_end = p_begin + len;

    assert(playlist);

    LOGD("parse_m3u8\n%s", buffer);

    char* line = read_line(p_begin, &p_read, p_end - p_begin);

    if (line == NULL) {
        return HLS_NOMEMORY;
    }

    p_begin = p_read;

    if (strncmp(line, "#EXTM3U", 7) != 0) {
        LOGE("missing #EXTM3U tag .. aborting");
        FREE(line);
        return HLS_FAILED;
    }

    FREE(line);
    line = NULL;

    /* What is the version ? */
    int version = 1;
    uint8_t* p = (uint8_t*)strstr((const char*)buffer, "#EXT-X-VERSION:");

    if (p != NULL) {
        uint8_t* tmp = NULL;
        char* psz_version = read_line(p, &tmp, p_end - p);

        if (psz_version == NULL) {
            return HLS_NOMEMORY;
        }

        int ret = sscanf((const char*)psz_version, "#EXT-X-VERSION:%d", &version);

        if (ret != 1) {
            LOGW("#EXT-X-VERSION: no protocol version found, assuming version 1.");
            version = MIN_PROTOCOL_VERSION;
        }

        FREE(psz_version);
        p = NULL;
    }

    /* Is it a live stream ? */
    playlist->b_live = (strstr((const char*)buffer, "#EXT-X-ENDLIST") == NULL) ? true : false;

    /* Is it a meta index file ? */
    bool b_meta = (strstr((const char*)buffer, "#EXT-X-STREAM-INF") == NULL) ? false : true;

    if (b_meta && b_parent_is_meta) { /* Nested meta playlists ?? */
        return HLS_FAILED;
    }

    int err = HLS_SUCCESS;

    if (b_meta) {
        LOGD("Meta playlist");

        /* M3U8 Meta Index file */
        do {
            /* Next line */
            line = read_line(p_begin, &p_read, p_end - p_begin);

            if (line == NULL) {
                break;
            }

            p_begin = p_read;

            if (strncmp(line, "#EXT-X-STREAM-INF", 17) == 0) {
                playlist->b_meta = true;

                char* uri = read_line(p_begin, &p_read, p_end - p_begin);

                if (uri == NULL) {
                    err = HLS_NOMEMORY;
                } else {
                    if (*uri == '#') {
                        LOGW("Skipping invalid stream-inf: %s", uri);
                        FREE(uri);
                    } else {
                        err = parse_stream_information(playlist, line, uri);
                        FREE(uri);
                    }
                }

                p_begin = p_read;
            }

            FREE(line);
            line = NULL;

            if (p_begin >= p_end) {
                break;
            }
        } while (err == HLS_SUCCESS);

        LOGD("%d streams loaded in Meta playlist", playlist->count);

        if (playlist->count == 0) {
            LOGE("No playable streams found in Meta playlist");
            err = HLS_FAILED;
        }
    } else {
        LOGD("%s Playlist HLS protocol version: %d",
                playlist->b_live ? "Live" : "VOD", version);

        hls_stream_playlist* stream = NULL;

        if (playlist->current_stream != NULL) {
            stream = playlist->current_stream;
        } else {
            /* No Meta playlist used */
            stream = hls_stream_playlist_new(playlist, 0, 0, playlist->url);

            if (stream) {
                /* Get TARGET-DURATION first */
                p = (uint8_t*)strstr((const char*)buffer, "#EXT-X-TARGETDURATION:");

                if (p) {
                    uint8_t* p_rest = NULL;
                    char* psz_duration = read_line(p, &p_rest, p_end - p);

                    if (psz_duration == NULL) {
                        return HLS_FAILED;
                    }

                    err = parse_target_duration(stream, psz_duration);
                    FREE(psz_duration);
                    p = NULL;
                }

                /* Store version */
                stream->version = version;
            } else {
                return HLS_NOMEMORY;
            }
        }

        bool media_sequence_loaded = false;
        int segment_duration = -1;

        do {
            /* Next line */
            line = read_line(p_begin, &p_read, p_end - p_begin);

            if (line == NULL) {
                break;
            }

            p_begin = p_read;

            if (strncmp(line, "#EXTINF", 7) == 0) {
                err = parse_segment_information(stream, line, &segment_duration);
            } else if (strncmp(line, "#EXT-X-TARGETDURATION", 21) == 0) {
                err = parse_target_duration(stream, line);
            } else if (strncmp(line, "#EXT-X-MEDIA-SEQUENCE", 21) == 0) {
                /* A Playlist file MUST NOT contain more than one EXT-X-MEDIA-SEQUENCE tag. */
                /* We only care about first one */
                if (!media_sequence_loaded) {
                    err = parse_media_sequence(playlist, stream, line);
                    media_sequence_loaded = true;
                }
            } else if (strncmp(line, "#EXT-X-KEY", 10) == 0) {
                err = parse_key(playlist, stream, line);
            } else if (strncmp(line, "#EXT-X-VERSION", 14) == 0) {
                err = parse_version(stream, line);
            } else if (strncmp(line, "#EXT-X-ENDLIST", 14) == 0) {
                playlist->b_live = false;
                LOGD("video on demand (vod) mode");
                err = HLS_SUCCESS;
            } else if ((strncmp(line, "#", 1) != 0) && (*line != '\0')) {
                err = parse_add_segment(stream, segment_duration, line);
                segment_duration = -1; /* reset duration */
            } else {
                LOGD("tag not supported: %s", line);
            }

            FREE(line);
            line = NULL;

            if (p_begin >= p_end) {
                break;
            }

        } while (err == HLS_SUCCESS);

        FREE(line);
    }

    return err;
}

static void choose_best_stream(hls_master_playlist* playlist) {
    assert(playlist);
    assert(playlist->first_stream);

    // TODO:
    hls_stream_playlist* best = playlist->first_stream;
    hls_stream_playlist* p = playlist->first_stream->next;
    while (p != NULL) {
        if (p->bandwidth > best->bandwidth) {
            best = p;
        }
        p = p->next;
    }

    LOGD("Choosing best quality. (bandwidth: %lld)", best->bandwidth);
    playlist->current_stream = best;
}

static int check_need_to_stop() {
    return s_stop_flag;
}

static uint8_t* get_last_line_feed_postion(uint8_t* buffer, const size_t len) {
    assert(buffer);
    assert(len > 0);

    uint8_t* p = buffer + len - 1;
    uint8_t* rend = buffer -1;
    while (p > rend) {
        if (*p == '\n') {
            return p;
        }
        p--;
    }

    return NULL;
}

static int parse_m3u8_url(const char* url, hls_master_playlist** playlist) {
    if (url == NULL || playlist == NULL) {
        LOGE("Invalid argument!");
        return HLS_FAILED;
    }

    if (s_adapter == NULL) {
        LOGE("s_adapter is NULL!");
        return HLS_FAILED;
    }

    uint8_t* data = NULL;
    size_t len = 0;
    if (s_adapter->get_short_data_from_url(url, check_need_to_stop,
            MAX_LIVE_M3U8_LENGTH, 0, &data, &len)) {
        LOGE("Get data from url failed.");
        return HLS_FAILED;
    }

    int ret = HLS_SUCCESS;
    bool is_vod = false;

    if (len >= MAX_LIVE_M3U8_LENGTH) {
        uint8_t* pos = get_last_line_feed_postion(data, len);
        if (pos == NULL) {
            LOGE("Download error data!");
            FREE(data);

            return HLS_FAILED;
        }

        len -= (data + len - pos + 1);
        is_vod = true;
    }

    hls_master_playlist* pl = (hls_master_playlist*)CALLOC(1, sizeof(hls_master_playlist), MEDIA);
    if (pl == NULL) {
        ret = HLS_NOMEMORY;
        goto failed;
    }

    pl->url = hls_strdup(url);
    if (pl->url == NULL) {
        ret = HLS_NOMEMORY;
        goto failed;
    }

    ret = parse_m3u8(pl, data, len, false);
    if (ret != HLS_SUCCESS) {
        goto failed;
    }

    if (pl->b_meta) {
        assert(is_vod == false);

        choose_best_stream(pl);

        if (pl->current_stream == NULL) {
            LOGE("current_stream is NULL!");
            ret = HLS_FAILED;
            goto failed;
        }

        FREE(data);
        data = NULL;

        if (s_adapter->get_short_data_from_url(pl->current_stream->url,
                check_need_to_stop, MAX_LIVE_M3U8_LENGTH, 0, &data, &len)) {
            LOGE("Get data from url failed.");
            ret = HLS_FAILED;
            goto failed;
        }

        if (len >= MAX_LIVE_M3U8_LENGTH) {
            uint8_t* pos = get_last_line_feed_postion(data, len);
            if (pos == NULL) {
                LOGE("Download error data!");
                ret = HLS_FAILED;
                goto failed;
            }

            len -= (data + len - pos + 1);
            is_vod = true;
        }

        ret = parse_m3u8(pl, data, len, true);
        if (ret != HLS_SUCCESS) {
            goto failed;
        }
    } else {
        pl->current_stream = pl->first_stream;
    }

    if (is_vod) {
        pl->current_stream->position = len;
        pl->b_live = false;
    }

    *playlist = pl;
    FREE(data);

    return HLS_SUCCESS;

failed:
    if (pl != NULL) {
        hls_master_playlist_free(pl);
        pl = NULL;
    }
    FREE(data);

    return ret;
}

static int get_stream_playlist(hls_master_playlist* playlist, hls_stream_playlist** stream) {
    *stream = NULL;

    hls_stream_playlist* old_stream = playlist->current_stream;
    hls_stream_playlist* new_stream =
            (hls_stream_playlist *)CALLOC(1, sizeof(hls_stream_playlist), MEDIA);

    if (new_stream == NULL) {
        return HLS_NOMEMORY;
    }

    new_stream->id = old_stream->id;
    new_stream->bandwidth = old_stream->bandwidth;
    new_stream->url = hls_strdup(old_stream->url);
    if (new_stream->url == NULL) {
        FREE(new_stream);
        return HLS_NOMEMORY;
    }

    playlist->current_stream = new_stream;
    *stream = new_stream;

    uint8_t* data = NULL;
    size_t len = 0;
    if (s_adapter->get_short_data_from_url(new_stream->url, check_need_to_stop,
            MAX_LIVE_M3U8_LENGTH, 0, &data, &len)) {
        LOGE("Get data from url failed.");
        playlist->current_stream = old_stream;
        return HLS_FAILED;
    }

    int err = parse_m3u8(playlist, data, len, playlist->b_meta);
    playlist->current_stream = old_stream;
    FREE(data);

    return err;
}

/*
 * Update master_playlist->current_stream to match new_stream
 * (which represents a downloaded, perhaps newer version of the same playlist)
 */
static int update_playlist(
        hls_master_playlist* playlist,
        hls_stream_playlist* new_stream,
        bool *stream_updated) {
    LOGD("updating hls stream (program-id=%d, bandwidth=%lld) has %d segments",
             new_stream->id, new_stream->bandwidth, new_stream->count);

    hls_stream_playlist* old_stream = playlist->current_stream;
    hls_segment* p = new_stream->first_segment;
    while (p != NULL) {
        hls_segment* next = p->next;
        hls_segment* segment = hls_segment_find(old_stream, p->sequence);
        if (segment) {
            assert(p->url);
            assert(segment->url);

            /* they should be the same */
            if ((p->duration != segment->duration) ||
                (strcmp(p->url, segment->url) != 0)) {
                LOGW("existing segment found with different content - resetting");
                LOGW("- duration: new=%d, old=%d", p->duration, segment->duration);
                LOGW("- file: new=%s", p->url);
                LOGW("        old=%s", segment->url);

                /* Resetting content */
                segment->duration = p->duration;
                FREE(segment->url);
                segment->url = hls_strdup(p->url);
                if (segment->url == NULL) {
                    LOGE("Failed updating segment %d - skipping it",  p->sequence);
                    hls_segment_free(p);
                    p = next;
                    continue;
                }

                FREE(segment->aes128.url);
                segment->aes128.url = p->aes128.url ? hls_strdup(p->aes128.url) : NULL;
            }
            hls_segment_free(p);
        } else {
            hls_segment* l = old_stream->last_segment;
            if (l == NULL) {
                old_stream->first_segment = old_stream->last_segment = p;
            } else {
                if (p->sequence <= l->sequence) {
                    LOGI("drop unexpected segment:%d", p->sequence);
                    hls_segment_free(p);
                    p = next;
                    continue;
                } else if ((l->sequence + 1) != p->sequence) {
                    LOGW("gap in sequence numbers found: new=%d expected %d",
                            p->sequence, l->sequence+1);
                } else {
                    // do nothing
                }

                old_stream->last_segment->next = p;
                old_stream->last_segment = p;
                old_stream->count++;
                p->next = NULL;
            }

            LOGD("- segment %d appended", p->sequence);

            *stream_updated = true;
        }
        p = next;
    }

    /* update meta information */
    old_stream->sequence = new_stream->sequence;
    old_stream->duration = (new_stream->duration == -1) ?
            old_stream->duration : new_stream->duration;

    new_stream->first_segment = new_stream->last_segment = NULL;

    return HLS_SUCCESS;
}

static int reload_playlist(hls_master_playlist* playlist) {
    LOGI("Reloading HLS live meta playlist");

    s_adapter->mutex_lock(playlist->lock);

    hls_stream_playlist* new_stream = NULL;
    if (get_stream_playlist(playlist, &new_stream) != HLS_SUCCESS) {
        if (new_stream != NULL) {
            FREE(new_stream);
        }

        s_adapter->mutex_unlock(playlist->lock);
        LOGE("reloading playlist failed");
        return HLS_FAILED;
    }

    bool stream_updated = false;
    /* merge playlists */
    if (update_playlist(playlist, new_stream, &stream_updated) != HLS_SUCCESS) {
        LOGW("failed updating HLS stream (id=%d, bandwidth=%lld)",
                 new_stream->id, new_stream->bandwidth);
    }

    hls_stream_playlist_free(new_stream);
    new_stream = NULL;

    if (stream_updated) {
        LOGD("stream has new segment!");
        playlist->current_stream->b_updated = true;
    }

    s_adapter->mutex_unlock(playlist->lock);

    return HLS_SUCCESS;
}

static int load_more_vod_playlist(hls_master_playlist* playlist, bool* end) {
    hls_stream_playlist* stream = playlist->current_stream;
    const size_t ONCE_LENGTH = 1024;
    uint8_t* data = NULL;
    size_t len = 0;
    if (s_adapter->get_short_data_from_url(stream->url, check_need_to_stop,
            ONCE_LENGTH, stream->position, &data, &len)) {
        LOGE("Get data from url failed.");
        return HLS_FAILED;
    }

    if (data == NULL || len < 1) {
        *end = true;
        return HLS_SUCCESS;
    }

    if (len >= ONCE_LENGTH) {
        uint8_t* pos = get_last_line_feed_postion(data, len);
        if (pos == NULL) {
            LOGE("Download error data!");
            FREE(data);

            return HLS_FAILED;
        }

        len -= (data + len - pos + 1);
        stream->position += len;
    }

    int err = HLS_SUCCESS;
    int segment_duration = -1;
    uint8_t* p_pos = NULL;
    uint8_t* p_begin = data;
    uint8_t* p_end = p_begin + len;
    char* line = NULL;

    do {
        /* Next line */
        line = read_line(p_begin, &p_pos, p_end - p_begin);
        if (line == NULL) {
            break;
        }

        p_begin = p_pos;

        if (strncmp(line, "#EXTINF", 7) == 0) {
            err = parse_segment_information(stream, line, &segment_duration);
        } else if ((strncmp(line, "#", 1) != 0) && (*line != '\0')) {
            s_adapter->mutex_lock(playlist->lock);
            err = parse_add_segment(stream, segment_duration, line);
            s_adapter->mutex_unlock(playlist->lock);

            segment_duration = -1;
        } else if (strncmp(line, "#EXT-X-ENDLIST", 14) == 0) {
            *end = true;
            break;
        } else {
            LOGD("Ignore line: %s", line);
        }

        FREE(line);
        line = NULL;
    } while (err == HLS_SUCCESS && p_begin < p_end);

    FREE(line);
    FREE(data);

    return err;
}

static void* reload_hls(void* p_this) {
    LOGI("reload thread start");
    hls_master_playlist* playlist = (hls_master_playlist*)p_this;

    int wait = 0;
    while (!check_need_to_stop()) {
        hls_stream_playlist* stream = playlist->current_stream;

        uint32_t now = s_adapter->current_time();
        if (now >= playlist->wakeup) {
            wait = 1000;

            /* reload the m3u8 if there are less than 5 segments what aren't downloaded */
            if (stream->count < 5) {
                if (playlist->b_live) {
                    if (reload_playlist(playlist) != HLS_SUCCESS) {
                        wait = 500;
                    }
                } else {
                    assert(stream->position > 0);
                    bool end = false;
                    if (load_more_vod_playlist(playlist, &end) != HLS_SUCCESS) {
                        wait = 500;
                        LOGW("load_more_vod_playlist error");
                    }
                    if (end) {
                        LOGI("All vod playlist are loaded.");
                        break;
                    }
                }
            }

            /* determine next time to update playlist */
            playlist->wakeup = now + stream->duration * wait * 1000;
        } else if (now < playlist->wakeup - stream->duration * 1000 * 1000) {
            LOGD("time is overflow");
            playlist->wakeup = now;
            continue;
        } else {
            // do nothing
        }

        wait = (playlist->wakeup - now) / 1000;
        s_adapter->wait(wait == 0 ? 1 : wait);
    }
    LOGD("reload thread stop");

    return NULL;
}

static int handle_segment_data(
        void* stream_ptr,
        void* segment_ptr,
        const uint8_t* data,
        size_t len,
        int pos) {
    assert(stream_ptr);
    assert(segment_ptr);
    hls_stream_playlist* stream = (hls_stream_playlist*)stream_ptr;
    hls_segment* segment = (hls_segment*)segment_ptr;
    int ret = HLS_SUCCESS;

    if (segment->aes128.url != NULL) {
        // decode AES-128 data
        void* aes_ctx = NULL;
        uint8_t* iv = NULL;
        if (segment->aes128.aes_ctx == NULL) {
            segment->aes128.aes_ctx = s_adapter->aes_context_create();
            if (segment->aes128.aes_ctx == NULL) {
                return HLS_NOMEMORY;
            }
            aes_ctx = segment->aes128.aes_ctx;

            if (s_adapter->aes_setkey_dec(aes_ctx, segment->aes128.aes_key) != HLS_SUCCESS) {
                return HLS_FAILED;
            }

            segment->aes128.iv = (uint8_t*)MALLOC(AES_BLOCK_SIZE, MEDIA);
            if (segment->aes128.iv == NULL) {
                return HLS_NOMEMORY;
            }
            iv = segment->aes128.iv;

            if (!stream->b_iv_loaded) {
                memset(iv, 0, AES_BLOCK_SIZE);
                iv[15] = segment->sequence & 0xff;
                iv[14] = (segment->sequence >> 8) & 0xff;
                iv[13] = (segment->sequence >> 16) & 0xff;
                iv[12] = (segment->sequence >> 24) & 0xff;
            } else {
                memcpy(iv, stream->aes_iv, AES_BLOCK_SIZE);
            }
        } else {
            aes_ctx = segment->aes128.aes_ctx;
            iv = segment->aes128.iv;
        }

        // remain is not 0 only when data is last buf of segment
        size_t remain = len % 16;
        uint8_t* output = (uint8_t*)MALLOC(remain == 0 ? len : (len + 16 - remain), MEDIA);
        if (s_adapter->aes_decrypt_cbc(aes_ctx, len - remain, iv, data, output) != 0) {
            return HLS_FAILED;
        }

        if (remain > 0) {
            uint8_t temp[16];
            memcpy(temp, data, remain);
            if (s_adapter->aes_decrypt_cbc(aes_ctx, 16, iv, temp, output) != 0) {
                return HLS_FAILED;
            }
        }
        ret = s_adapter->output_ts_data(output, len, pos);
        FREE(output);
    } else {
        ret = s_adapter->output_ts_data(data, len, pos);
    }

    return ret;
}

static int download_segment(hls_stream_playlist* stream, hls_segment* segment, bool b_first) {
    assert(segment);
    LOGI("download_segment %s", segment->url);
    hls_download_context* context = (hls_download_context*)MALLOC(
                sizeof(hls_download_context), MEDIA);
    if (context == NULL) {
        return HLS_NOMEMORY;
    }

    context->b_first_segment = b_first;
    context->stream = stream;
    context->segment = segment;
    context->handle_segment_data = handle_segment_data;

    int ret = s_adapter->download_segment(segment->url, check_need_to_stop, context);
    FREE(context);
    LOGI("download_segment finished!");
    return ret;
}

static int download_segment_keys(hls_stream_playlist* stream) {
    hls_segment* seg = stream->first_segment;
    hls_segment* prev_seg = NULL;

    for (; seg != NULL; seg = seg->next) {
        if (seg->aes128.url == NULL) {
            continue;   // No key to load
        }

        if (seg->aes128.b_key_loaded) {
            prev_seg = seg;
            continue;
        }

        /* if the key has not changed, and already available from previous segment,
         * try to copy it, and don't load the key
         */
        if (prev_seg != NULL && prev_seg->aes128.b_key_loaded
                && strcmp(seg->aes128.url, prev_seg->aes128.url) == 0) {
            memcpy(seg->aes128.aes_key, prev_seg->aes128.aes_key, AES_BLOCK_SIZE);
            seg->aes128.b_key_loaded = true;
            continue;
        }

        size_t len = 0;
        if (s_adapter->get_short_data_from_url(seg->aes128.url, check_need_to_stop,
                MAX_LIVE_M3U8_LENGTH, 0, &seg->aes128.aes_key, &len)) {
            LOGE("Get aes key from url failed.");
            return HLS_FAILED;
        }

        if (len != AES_BLOCK_SIZE) {
            FREE(seg->aes128.aes_key);
            seg->aes128.aes_key = NULL;
            return HLS_FAILED;
        }

        seg->aes128.b_key_loaded = true;

        prev_seg = seg;
    }

    return HLS_SUCCESS;
}

static int initialize_hls() {
    hls_adapter* adapter = create_hls_adapter();
    if (adapter == NULL) {
        LOGE("initialize_hls failed!");
        return HLS_FAILED;
    }

    if (adapter->get_short_data_from_url == NULL) {
        LOGE("adapter->get_short_data_from_url is NULL.");
        return HLS_FAILED;
    }

    if (adapter->current_time == NULL) {
        LOGE("adapter->current_time is NULL.");
        return HLS_FAILED;
    }

    if (adapter->wait == NULL) {
        LOGE("adapter->wait is NULL.");
        return HLS_FAILED;
    }

    if (adapter->thread_create == NULL) {
        LOGE("adapter->thread_create is NULL.");
        return HLS_FAILED;
    }

    if (adapter->thread_destroy == NULL) {
        LOGE("adapter->thread_destroy is NULL.");
        return HLS_FAILED;
    }

    if (adapter->thread_start == NULL) {
        LOGE("adapter->thread_start is NULL.");
        return HLS_FAILED;
    }

    if (adapter->mutex_create == NULL) {
        LOGE("adapter->mutex_create is NULL.");
        return HLS_FAILED;
    }

    if (adapter->mutex_lock == NULL) {
        LOGE("adapter->mutex_lock is NULL.");
        return HLS_FAILED;
    }

    if (adapter->mutex_unlock == NULL) {
        LOGE("adapter->mutex_unlock is NULL.");
        return HLS_FAILED;
    }

    if (adapter->mutex_destroy == NULL) {
        LOGE("adapter->mutex_destroy is NULL.");
        return HLS_FAILED;
    }

    if (adapter->aes_context_create == NULL) {
        LOGE("adapter->aes_context_create is NULL.");
        return HLS_FAILED;
    }

    if (adapter->aes_setkey_dec == NULL) {
        LOGE("adapter->aes_setkey_dec is NULL.");
        return HLS_FAILED;
    }

    if (adapter->aes_decrypt_cbc == NULL) {
        LOGE("adapter->aes_decrypt_cbc is NULL.");
        return HLS_FAILED;
    }

    if (adapter->aes_context_destroy == NULL) {
        LOGE("adapter->aes_context_destroy is NULL.");
        return HLS_FAILED;
    }

    if (adapter->download_segment == NULL) {
        LOGE("adapter->download_segment is NULL.");
        return HLS_FAILED;
    }

    if (adapter->output_ts_data == NULL) {
        LOGE("adapter->output_ts_data is NULL.");
        return HLS_FAILED;
    }

    s_adapter = adapter;

    return HLS_SUCCESS;
}

static void print_playlist(hls_master_playlist* playlist) {
    PRINT("Print m3u8 playlist:\n");
    if (playlist == NULL) {
        PRINT("playlist is NULL\n");
        return ;
    }

    if (playlist->b_error) {
        PRINT("playlist parse error!\n");
    } else {
        PRINT("playlist is %s\n", playlist->b_live ? "live" : "VOD");
        PRINT("url:%s\n", playlist->url);
        hls_stream_playlist* stream = NULL;
        if (playlist->b_meta) {
            PRINT("playlist is meta\n");
            PRINT("Media playlist count:%d\n", playlist->count);
            hls_stream_playlist* p = playlist->first_stream;
            int i = 0;
            while (p != NULL) {
                PRINT("stream[%d]:\n", i);
                PRINT("url:%s\n", p->url);
                PRINT("id:%d, bandwidth:%lld\n", p->id, p->bandwidth);
                p = p->next;
                i++;
            }
            stream = playlist->current_stream;
        } else {
            stream = playlist->first_stream;
            PRINT("playlist is stream\n");
            PRINT("url:%s\n", stream->url);
            PRINT("id:%d, bandwidth:%lld\n", stream->id, stream->bandwidth);
        }

        PRINT("stream_playlist:\n");
        PRINT("version:%d\n", stream->version);
        PRINT("sequence:%d\n", stream->sequence);
        PRINT("duration:%d\n", stream->duration);
        PRINT("bandwidth:%lld\n", stream->bandwidth);
        PRINT("segment count:%d\n", stream->count);

        hls_segment* p = stream->first_segment;
        int i = 0;
        while (p != NULL) {
            PRINT("segment[%d]:\n", i);
            PRINT("    url:%s\n", p->url);
            PRINT("    duration:%d\n", p->duration);
            PRINT("    sequence:%d\n", p->sequence);
            PRINT("    aes_key url:%s\n", p->aes128.url);
            PRINT("    aes_key:");
            if (p->aes128.aes_key != NULL) {
                for (int i = 0; i < AES_BLOCK_SIZE; i++) {
                    PRINT("%02x", p->aes128.aes_key[i]);
                }
            } else {
                PRINT("null");
            }
            PRINT("\n");
            PRINT("    aes_key b_key_loaded:%d\n", p->aes128.b_key_loaded);
            p = p->next;
            i++;
        }
    }
}

int start_hls(const char* url) {
    if (s_adapter == NULL) {
        if (initialize_hls() != HLS_SUCCESS) {
            LOGE("Hls adapter initialize failed!");
            return HLS_FAILED;
        }
    }

    LOGI("start_hls %s", url);
#ifdef HEAP_MONITOR
    show_heap_info();
#endif

    s_stop_flag = 0;

    hls_master_playlist* playlist = NULL;
    int ret = parse_m3u8_url(url, &playlist);
    if (ret != HLS_SUCCESS) {
        LOGE("parse m3u8 error:%d", ret);
        return ret;
    }

    int retry = 0;
    bool b_first = true;

    playlist->lock = s_adapter->mutex_create();
    if (playlist->lock == NULL) {
        ret = HLS_FAILED;
        goto failed;
    }

    if (s_reload_thread) {
        s_adapter->thread_destroy(s_reload_thread);
    }

    ret = download_segment_keys(playlist->current_stream);
    if (ret != HLS_SUCCESS) {
        goto failed;
    }

    if (playlist->b_live || playlist->current_stream->position > 0) {
        // start reload playlist thread

        playlist->wakeup = s_adapter->current_time() +
            playlist->current_stream->duration * 1000 * 1000;

        s_reload_thread = s_adapter->thread_create();
        if (s_reload_thread == NULL) {
            LOGE("Create reload thread failed!");
            ret = HLS_FAILED;
            goto failed;
        }

        s_adapter->thread_start(s_reload_thread, reload_hls, playlist);
    }

    // print_playlist(playlist);

    while (!check_need_to_stop()) {
        s_adapter->mutex_lock(playlist->lock);
        hls_stream_playlist* stream = playlist->current_stream;
        LOGD("stream->count = %d\n", stream->count);

        if (stream->count > 0) {
            retry = 0;

            if (stream->b_updated) {
                ret = download_segment_keys(stream);
                if (ret != HLS_SUCCESS) {
                    goto failed;
                }
                stream->b_updated = false;
            }

            // download first segment
            hls_segment* segment = stream->first_segment;
            s_adapter->mutex_unlock(playlist->lock);

            download_segment(stream, segment, b_first);
            b_first = false;

            // remove first segment
            s_adapter->mutex_lock(playlist->lock);
            stream->first_segment = segment->next;
            if (segment == stream->last_segment) {
                stream->last_segment = NULL;
            }
            stream->count--;
            s_adapter->mutex_unlock(playlist->lock);

            hls_segment_free(segment);
        } else if (!playlist->b_live) {
            s_adapter->mutex_unlock(playlist->lock);
            break;
        } else {
            s_adapter->mutex_unlock(playlist->lock);

            // if (++retry > 5) {
            //     LOGW("Http live stream error, stop it!");
            //     stop_hls();
            // } else {
                LOGI("no more new segment, wait a moment...");
                s_adapter->wait(5000);
            // }
        }
    }

    if (check_need_to_stop()) {
        ret = 1;
    }

failed:
    if (s_reload_thread) {
        s_adapter->thread_destroy(s_reload_thread);
    }
    hls_master_playlist_free(playlist);

    LOGI("start_hls end");
#ifdef HEAP_MONITOR
    show_heap_info();
#endif

    return ret;
}

int stop_hls() {
    s_stop_flag = 1;

    if (s_reload_thread) {
        s_adapter->thread_destroy(s_reload_thread);
        s_reload_thread = NULL;
    }

    return 0;
}
